package com.ccc.test.util;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;

/**
 * 使用ZXing操作二维码(二维码生成和读的工具类), 二维码图片默认PNG格式
 *
 * @author
 * @date 2019/06/07
 */
public class QrCodeUtils {

    /**
     * 文本编码格式：UTF-8
     */
    public static final String CHARSET_DEF = "UTF-8";

    /**
     * 图案颜色：黑色
     */
    public static final int BLACK = 0xFF000000;

    /**
     * 背景色：白色
     */
    public static final int WHITE = 0xFFFFFFFF;

    /**
     * 图片最大值：1024
     */
    public static final int MAX_SIZE = 1024;

    /**
     * 图片最小值：400
     */
    public static final int MIN_SIZE = 400;

    /**
     * 编码提示配置
     */
    public static final Map<EncodeHintType, Object> HINTS = new Hashtable<EncodeHintType, Object>() {
        private static final long serialVersionUID = 1L;

        {
            // 指定纠错等级,纠错级别（L 7%、M 15%、Q 25%、H 30%）
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            // 内容所使用字符集编码
            put(EncodeHintType.CHARACTER_SET, CHARSET_DEF);
            //           put(EncodeHintType.MAX_SIZE, MAX_SIZE);//设置图片的最大值
            //           put(EncodeHintType.MIN_SIZE, MIN_SIZE);//设置图片的最小值
            put(EncodeHintType.MARGIN, 0);//设置二维码边的空度，非负数

        }
    };

    /**
     * 解码配置
     */
    public static final Map<DecodeHintType, Object> DECODER_HINTS = new Hashtable<DecodeHintType, Object>() {
        private static final long serialVersionUID = 11L;

        {
            put(DecodeHintType.CHARACTER_SET, CHARSET_DEF);   // 防止中文乱码
        }
    };

    /**
     * 生成包含字符串信息的二维码图片
     *
     * @param content    内容
     * @return
     * @throws
     */
    public static BufferedImage defaultBufferedImage(String content)
            throws WriterException, IOException {
        return createBufferedImage(content, 500);
    }

    /**
     * 生成包含字符串信息的二维码图片
     *
     * @param content    内容
     * @param qrCodeSize 二维码尺寸
     * @return
     * @throws
     */
    public static BufferedImage createBufferedImage(String content, int qrCodeSize)
            throws WriterException, IOException {
        content = new String(content.getBytes(CHARSET_DEF), "ISO-8859-1");
        Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<>();
        hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        //创建比特矩阵(位矩阵)的QR码编码的字符串
        BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);
        // 使BufferedImage勾画QRCode  (matrixWidth 是行二维码像素点)
        int matrixWidth = byteMatrix.getWidth();
        BufferedImage image = new BufferedImage(matrixWidth - 200, matrixWidth - 200, BufferedImage.TYPE_INT_RGB);
        image.createGraphics();
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, matrixWidth, matrixWidth);
        // 使用比特矩阵画并保存图像
        graphics.setColor(Color.BLACK);
        for (int i = 0; i < matrixWidth; i++) {
            for (int j = 0; j < matrixWidth; j++) {
                if (byteMatrix.get(i, j)) {
                    graphics.fillRect(i - 100, j - 100, 1, 1);
                }
            }
        }
        return image;
    }


    /**
     * 生成包含字符串信息的二维码图片
     *
     * @param outputStream 输出流
     * @param content      内容
     * @param qrCodeSize   二维码尺寸
     * @param imageFormat  图片格式(默认PNG格式)
     * @return
     * @throws
     */
    public static boolean createQrCode(OutputStream outputStream, String content, int qrCodeSize, String imageFormat)
            throws WriterException, IOException {
        return ImageIO.write(createBufferedImage(content, qrCodeSize), imageFormat, outputStream);
    }

    /**
     * 生成包含字符串信息的二维码图片
     *
     * @param fileFullPath 文件完整路径
     * @param content      内容
     * @param qrCodeSize   二维码尺寸
     * @param imageFormat  图片格式
     * @return
     * @throws WriterException
     * @throws IOException
     */
    public static boolean createQrCode(String fileFullPath, String content, int qrCodeSize, String imageFormat)
            throws WriterException, IOException {
        return createQrCode(new File(fileFullPath), content, qrCodeSize, imageFormat);
    }

    /**
     * 生成包含字符串信息的二维码图片
     *
     * @param file        文件
     * @param content     内容
     * @param qrCodeSize  二维码尺寸
     * @param imageFormat 图片格式
     * @return
     * @throws WriterException
     * @throws IOException
     */
    public static boolean createQrCode(File file, String content, int qrCodeSize, String imageFormat)
            throws WriterException, IOException {
        return createQrCode(new FileOutputStream(file), content, qrCodeSize, imageFormat);
    }

    /**
     * 生成带Logo的条形码
     *
     * @param qrCodeSrc    条形码文件
     * @param logoSrc      Logo文件
     * @param content      内容
     * @param qrCodeWidth  条形码宽度
     * @param qrCodeHeight 条形码高度
     * @param imageFormat  条形码文件格式(默认PNG格式)
     * @return
     */
    public static boolean createQrCodeAndLogo(File qrCodeSrc, File logoSrc, String content, int qrCodeWidth,
                                              int qrCodeHeight, String imageFormat)
            throws WriterException, IOException {
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        BufferedImage image = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);
        // 开始利用二维码数据创建Bitmap图片，分别设为黑（0xFFFFFFFF）白（0xFF000000）两色

        BitMatrix matrix = multiFormatWriter.encode(content,//要编码的内容
                //编码类型，目前zxing支持：Aztec 2D,CODABAR 1D format,Code 39 1D,Code 93 1D ,Code 128 1D,
                //Data Matrix 2D , EAN-8 1D,EAN-13 1D,ITF (Interleaved Two of Five) 1D,
                //MaxiCode 2D barcode,PDF417,QR Code 2D,RSS 14,RSS EXPANDED,UPC-A 1D,UPC-E 1D,UPC/EAN extension,UPC_EAN_EXTENSION
                BarcodeFormat.QR_CODE,
                qrCodeWidth, //条形码的宽度
                qrCodeHeight, //条形码的高度
                HINTS);//生成条形码时的一些配置,此项可选
        for (int x = 0; x < qrCodeWidth; x++) {
            for (int y = 0; y < qrCodeHeight; y++) {
                image.setRGB(x, y, (matrix.get(x, y) ? BLACK : WHITE));
            }
        }
        int width = image.getWidth();
        int height = image.getHeight();
        if (Objects.nonNull(logoSrc) && logoSrc.exists()) {
            // 构建绘图对象
            Graphics2D g = image.createGraphics();
            // 读取Logo图片
            BufferedImage logo = ImageIO.read(logoSrc);
            // 开始绘制logo图片
            g.drawImage(logo, width * 2 / 5, height * 2 / 5, width * 2 / 10, height * 2 / 10, null);
            BasicStroke stroke = new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
            g.setStroke(stroke);// 设置笔画对象
            //指定弧度的圆角矩形
            RoundRectangle2D.Float round = new RoundRectangle2D.Float(width / 5 * 2, height / 5 * 2, width / 5, height / 5, 20, 20);
            g.setColor(Color.WHITE);
            g.draw(round);// 绘制圆弧矩形
            //设置logo 有一道灰色边框
            BasicStroke stroke2 = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
            g.setStroke(stroke2);// 设置笔画对象
            RoundRectangle2D.Float round2 = new RoundRectangle2D.Float(width / 5 * 2 + 2, height / 5 * 2 + 2, width / 5 - 4, height / 5 - 4, 20, 20);
            g.setColor(new Color(128, 128, 128));
            g.draw(round2);// 绘制圆弧矩形

            g.dispose();
            logo.flush();
        }
        image.flush();
        return ImageIO.write(image, imageFormat, qrCodeSrc);
    }

    /**
     * 生成带Logo的条形码
     *
     * @param qrCodeSrc    条形码文件路径
     * @param logoSrc      Logo文件路径
     * @param content      内容
     * @param qrCodeWidth  条形码宽度
     * @param qrCodeHeight 条形码高度
     * @param imageFormat  条形码文件格式
     * @return
     * @throws WriterException
     * @throws IOException
     */
    public static boolean createQrCodeAndLogo(String qrCodeSrc, String logoSrc, String content, int qrCodeWidth,
                                              int qrCodeHeight, String imageFormat)
            throws WriterException, IOException {
        return createQrCodeAndLogo(new File(qrCodeSrc), new File(logoSrc), content, qrCodeWidth, qrCodeHeight, imageFormat);
    }


    /**
     * 读二维码并输出携带的信息
     *
     * @param inputStream 输入流
     * @return
     * @throws IOException
     * @throws ReaderException
     */
    public static Result readQrCode(InputStream inputStream) throws IOException, ReaderException {
        //从输入流中获取字符串信息
        BufferedImage image = ImageIO.read(inputStream);
        //将图像转换为二进制位图源
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        /*//解码设置编码方式为：utf-8
        Map<DecodeHintType, String> hints = new Hashtable<>();
        hints.put(DecodeHintType.CHARACTER_SET, CharacterSetECI.UTF8.name());   // 防止中文乱码*/
        QRCodeReader reader = new QRCodeReader();
        return reader.decode(bitmap, DECODER_HINTS);
    }

    /**
     * 根据url对象进行读取二维码
     *
     * @param url URL对象
     * @return
     * @throws MalformedURLException
     * @throws IOException
     * @throws ReaderException
     */
    public static Result readQrCode(URL url) throws MalformedURLException, IOException, ReaderException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoInput(true);
        conn.connect();
        InputStream is = conn.getInputStream();
        return readQrCode(is);
    }

    /**
     * 根据二维码图片路径读取
     *
     * @param url 图片路径
     * @return
     * @throws MalformedURLException
     * @throws IOException
     * @throws ReaderException
     */
    public static Result readQrCodeByUrl(String url) throws MalformedURLException, IOException, ReaderException {
        return readQrCode(new URL(url));
    }

    /**
     * 读二维码并输出携带的信息
     *
     * @param fileFullPath 文件完整路径
     * @return
     * @throws IOException
     * @throws ReaderException
     */
    public static Result readQrCode(String fileFullPath) throws IOException, ReaderException {
        return readQrCode(new File(fileFullPath));
    }

    /**
     * 读二维码并输出携带的信息
     *
     * @param file 文件
     * @return
     * @throws IOException
     * @throws ReaderException
     */
    public static Result readQrCode(File file) throws IOException, ReaderException {
        return readQrCode(new FileInputStream(file));
    }

}